﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using CoreEx.Common.Proxy;
using LinFu.IoC.Configuration;
using LinFu.IoC.Interfaces;
using LinFu.IoC;
using Mono.Cecil;
using LinFu.Reflection.Emit;
using Mono.Cecil.Cil;
using CoreEx.Common.Extensions;
using System.Globalization;

namespace CoreEx.Common.Implementation.Proxy
{
    [Implements(typeof(ITypeModifier))]
    public class ConstructorModifier : ITypeModifier, IInitialize
    {

        private IInitializationInfoEmitter _initializationInfoEmitter;

        public void BeforeConstruction(Type originalBaseType, IEnumerable<Type> baseInterfaces, ModuleDefinition module, TypeDefinition targetType)
        {
            
        }

        public void AfterConstruction(Type originalBaseType, IEnumerable<Type> baseInterfaces, ModuleDefinition module, TypeDefinition targetType)
        {

            //Import the intercept method that will be called to notify the interceptor instance. 
            MethodReference interceptMethod 
                = targetType.Module.ImportMethod("Intercept", typeof(IConstructorInterceptor));
                                                                                
            //Import the Object.GetType method 
            MethodReference getType = targetType.Module.Import(typeof (object).GetMethod("GetType"));

            //Import the Type.GetMethod that is used to get reference to the static Interceptor property
            MethodReference getMethod = targetType.Module.Import(typeof(Type).GetMethod("GetMethod",new []{typeof(string),typeof(BindingFlags)} ));
            
            //Import the MethodInfo.Invoke method that is used to invoke the intercept get method
            MethodReference invokeMethod =
                targetType.Module.Import(typeof (MethodInfo).GetMethod("Invoke",
                                                                           new []
                                                                               {
                                                                                   typeof (object), typeof (BindingFlags),
                                                                                   typeof (Binder), typeof (object[]),
                                                                                   typeof (CultureInfo)
                                                                               }));
          
            //Get a reference to the parameterless constructor on the proxy type
            MethodDefinition constructorDefinition = targetType.Constructors[0];

            //Get at reference to constructors CilWorker
            CilWorker cilWorker = constructorDefinition.Body.CilWorker;

            //Find the instruction that represents OpCodes.Ret
            Instruction returnInstruction = constructorDefinition.Body.Instructions[constructorDefinition.Body.Instructions.Count-1];

            //Remove it from the current implementation
            cilWorker.Remove(returnInstruction);
            
                                                      
            //Declare a variable to hold the interceptor instance
            VariableDefinition interceptorVariable = constructorDefinition.AddLocal(typeof(IConstructorInterceptor));

            VariableDefinition typeVariable = constructorDefinition.AddLocal(typeof(Type));

            VariableDefinition methodInfoVariable = constructorDefinition.AddLocal(typeof(MethodInfo));
            
            // =   constructorDefinition.AddLocal(typeof (InitalizationInfo));            

            //Create a no operation instruction 
            Instruction endLabel = cilWorker.Create(OpCodes.Nop);

            //Get the type of the current proxy type
            //Type proxyType = this.GetType();
            cilWorker.Emit(OpCodes.Ldarg_0);
            cilWorker.Emit(OpCodes.Callvirt, getType);
            cilWorker.Emit(OpCodes.Stloc, typeVariable);
            
            //Get a reference to the "get_ConstructorInterceptor" method                     
            cilWorker.Emit(OpCodes.Ldloc, typeVariable);
            cilWorker.Emit(OpCodes.Ldstr, "get_ConstructorInterceptor");            
            cilWorker.Emit(OpCodes.Ldc_I4, (int)(BindingFlags.Public | BindingFlags.Static));
            cilWorker.Emit(OpCodes.Callvirt, getMethod);
            cilWorker.Emit(OpCodes.Stloc, methodInfoVariable);

            //Check to see if the methodInfo was found
            cilWorker.Emit(OpCodes.Ldloc, methodInfoVariable);
            cilWorker.Emit(OpCodes.Brfalse, endLabel);

            //Invoke the get method to get a reference to the interceptor instance
            cilWorker.Emit(OpCodes.Ldloc, methodInfoVariable);
            cilWorker.Emit(OpCodes.Ldnull);
            cilWorker.Emit(OpCodes.Ldc_I4, (int) (BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod));
            cilWorker.Emit(OpCodes.Ldnull);
            cilWorker.Emit(OpCodes.Ldnull);
            cilWorker.Emit(OpCodes.Ldnull);
            cilWorker.Emit(OpCodes.Callvirt, invokeMethod);
            cilWorker.Emit(OpCodes.Stloc, interceptorVariable);

            //Check to see if the interceptor property has been set.
            cilWorker.Emit(OpCodes.Ldloc, interceptorVariable);
            cilWorker.Emit(OpCodes.Brfalse, endLabel);

            //Create a new InitializationInfo instance
            VariableDefinition initializationInfoVariable = _initializationInfoEmitter.Emit(constructorDefinition, originalBaseType, baseInterfaces);

            //Call the IConstructorInterceptor.Intercept method
            cilWorker.Emit(OpCodes.Ldloc, interceptorVariable);
            cilWorker.Emit(OpCodes.Ldloc, initializationInfoVariable);
            cilWorker.Emit(OpCodes.Callvirt, interceptMethod);

            //Add the endlabel(OpCodes.Nop)
            cilWorker.Append(endLabel);
            //Resinsert the OpCodes.Ret instruction
            cilWorker.Append(returnInstruction);            
        }

        public void Initialize(IServiceContainer source)
        {
            _initializationInfoEmitter = source.GetService<IInitializationInfoEmitter>();
        }
    }
}
